/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Controller;

import Supporter.OurLogger;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URL;
import java.util.logging.Level;
import org.apache.http.ConnectionClosedException;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpVersion;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HTTP;
import org.jdom.JDOMException;

/**
 *
 * @author Ivan
 */
public class ClientHandler implements Runnable {

    DefaultHttpServerConnection conn;
    OurListener invoker;

    ClientHandler(Socket requester, OurListener invoker) throws IOException {
        conn = createHttpConn(requester);
        this.invoker = invoker;
    }

    private DefaultHttpServerConnection createHttpConn(Socket requester) throws IOException {
        HttpParams params = new BasicHttpParams();
        conn = new DefaultHttpServerConnection();
        conn.bind(requester, params);
        return conn;
    }

    @Override
    public void run() {
        try {
            while (true) {
                RequestOurHttp or = getRequest(conn);
                String logStr = createLog(or);
                OurLogger.logNotError(this.toString(), "-run(): " + logStr, Level.INFO);

                processClientReq(or);
            }
        } catch (ConnectionClosedException ex) { // client terminated connection
        } catch (IllegalStateException ex) {// client terminated connection
        } catch (Exception ex) {
            OurLogger.logError(this, "run()", ex, Level.SEVERE);
        }
    }

    private RequestOurHttp getRequest(DefaultHttpServerConnection conn) throws HttpException, IOException {
        BasicHttpRequest request = (BasicHttpRequest) conn.receiveRequestHeader();
        RequestOurHttp or = new RequestOurHttp(request.getRequestLine());
        or.setHeaders(request.getAllHeaders());
        return or;
    }

    private String createLog(RequestOurHttp or) {
        String logStr = " Receive Http Request\n";
        for (Header header : or.getAllHeaders()) {
            logStr += header.toString() + "\n";
        }
        logStr += "\n";
        logStr += "Method:" + or.getRequestLine().getMethod() + "\n";
        logStr += "URI: " + or.getRequestLine().getUri() + "\n";
        return logStr;
    }

    private void processClientReq(RequestOurHttp or) throws IOException, ClassNotFoundException, HttpException, JDOMException {
        String uri = trimSlashes(or.getRequestLine().getUri());
        uri = uri.replace("%20", " ");
        String referer = "";
        String redirectURL = "";
        String responseType = null;

        if (or.getHeaders("Referer").length == 1) {
            //Browser request web-objects automatically.            
            referer = new URL(or.getHeaders("Referer")[0].getValue()).getPath();
            referer = trimSomeFileTypes(referer);
            referer = trimSlashes(referer);
            uri = removeRefererFromURI(uri, referer);
            uri = processURIForJavaScript(uri);
            referer = referer.replace("%20", " ");
        }

        String path = null;
        if (uri.equals("/ErrorPage/index.html") || referer.equals("/ErrorPage") || referer.contains("/ErrorPage/")) {
            path = (referer + uri).substring(1);
        } else {
            VirtualDirectory vd = VirtualDirectory.getRealPath((referer + uri).substring(1));
            path = vd.getExistFileOrDirectory() ? vd.getPatch() : null;

            if (path == null) {
                if (or.getHeaders("Referer").length == 0) {
                    path = "ErrorPage/index.html";
                    redirectURL = "http://" + or.getHeaders("Host")[0].getValue() + "/ErrorPage/index.html";
                }
            } else {
                if (path.contains("index.html") && uri.charAt(uri.length() - 1) != '/') {
                    redirectURL = "http://" + or.getHeaders("Host")[0].getValue() + uri + '/';
                }

                responseType = getResponseType(path, referer);
            }
        }

        response(conn, path, responseType, redirectURL);
    }

    private String trimSlashes(String uri) {
        int i = 0;
        int n = uri.length();
        String trimed = "/";
        while (i < n && uri.charAt(i) == '/') {            
            i++;
        }        

        while (i < n) {
            trimed += uri.charAt(i);
            i++;
            while (i < n && uri.charAt(i) == '/') {
                i++;
            }

            if (uri.charAt(i - 1) == '/') {
                trimed += '/';
            }
        }

        return trimed;
    }

    private String processURIForJavaScript(String uri) {
        if (uri.contains(".js")) {
            uri = uri.substring(0, uri.lastIndexOf(".js") + 3);
        }
        return uri;
    }

    private String removeRefererFromURI(String uri, String referer) {
        if (uri.contains(referer)) {
            uri = uri.substring(referer.length());
        } else {
            int numCut = countNumSlash(referer) - 1;// After the first time (a/b/c), the URI is like: a/b/...

            for (int i = 0; i < numCut; i++) {
                uri = uri.substring(1);
                uri = uri.substring(uri.indexOf('/'));
            }
        }
        return uri.charAt(0) == '/' ? uri : '/' + uri;
    }

    /**
     * Return null if browser requests web-objects automatically.
     * Return null if user request HTML, HTM, XML files.
     * Return plain_text if user request txt, bat                             
     * Return octet_stream for other requests.
     */
    private String getResponseType(String uri, String referer) {
        if (referer.length() > 0) {
            //Browser request web-objects automatically.
            return null;
        }

        for (String textType : Constants.WEB_FILE_TYPES) {
            if (uri.indexOf(textType) != -1) {
                return null;
            }
        }

        for (String textType : Constants.PLAIN_TEXT_FILE_TYPES) {
            if (uri.indexOf(textType) != -1) {
                return HTTP.PLAIN_TEXT_TYPE;
            }
        }

        return HTTP.OCTET_STREAM_TYPE;
    }

    /**
     * If requested file is binary, the browser will ask for downloading regardless of the parameter fileType.
     * In the other hand, the parameter fileType decides how the browser handles the response:
     * "text/plain"-browser will show plain text (even web-page file type);
     * "application/octet-stream"-browser will ask for downloading the file;
     * null-browser will show picture or web-page, ... depend on objects it was received.
     */
    private void response(DefaultHttpServerConnection conn, String path, String fileType, String trueReferer) throws IOException, UnsupportedEncodingException, HttpException {
        ResponseOurHttp response = new ResponseOurHttp(HttpVersion.HTTP_1_1, 200, "OK");

        HttpEntity entity = new FileEntity(new File(path), fileType);
        if (fileType != null) {
            // force to set content type for showing plain_text or downloading octet_stream)
            response.addHeader(entity.getContentType());
        } else {
            // let the browser set content type for showing webpage and web-objects.
        }
        response.setEntity(entity);
        if (trueReferer.length() > 0) {
            response.setStatusCode(302);
            response.addHeader("Location", trueReferer);
        }

        conn.sendResponseHeader(response);
        conn.sendResponseEntity(response);
        conn.close();
    }

    private int countNumSlash(String uri) {
        int count = 0;
        for (int i = 0; i < uri.length(); i++) {
            if (uri.charAt(i) == '/') {
                count++;
            }
        }

        return count;
    }

    private String trimSomeFileTypes(String referer) {
        if (referer.contains(".css") || referer.contains(".html") || referer.contains(".htm") || referer.contains(".js")) {
            String trueReferer = referer.substring(0, referer.lastIndexOf('/'));
            return trueReferer;
        }

        return referer;
    }
}
